package javassist;

import javassist.bytecode.Bytecode;
import javassist.bytecode.Descriptor;
import sf.tools.reflect.BeanUtils;

import java.util.ArrayList;
import java.util.List;

public class JavassistUtils {

    public static boolean setLoad(Bytecode bytecode, String desc, int index) {
        Class<?> w;
        String pdesc = Descriptor.getParamDescriptor(desc);
        String desc1 = pdesc.replace("(", "").replace(")", "");
        List<String> types = new ArrayList<>(4);
        StringBuilder sb = new StringBuilder();
        boolean e = false;
        for (int i = 0; i < desc1.length(); i++) {
            char c = desc1.charAt(i);
            if (c == 'L') {
                e = true;
            }
            if (e) {
                sb.append(c);
            }
            if (c == ';') {
                e = false;
            }
            if (!e && sb.length() == 0) {
                types.add(String.valueOf(c));
            }
            if (!e && sb.length() > 0) {
                types.add(sb.toString());
                sb.delete(0, sb.length());
            }
        }
        String type = "";
        if (!types.isEmpty()) {
            type = types.get(0);
        }
        switch (type) {
            case "Z":
                bytecode.addIload(index);
                w = Boolean.class;
                break;
            case "B":
                bytecode.addIload(index);
                w = Byte.class;
                break;
            case "S":
                bytecode.addIload(index);
                w = Short.class;
                break;
            case "C":
                bytecode.addIload(index);
                w = Character.class;
                break;
            case "I":
                bytecode.addIload(index);
                w = Integer.class;
                break;
            case "J":
                bytecode.addLload(index);
                w = Long.class;
                break;
            case "F":
                bytecode.addFload(index);
                w = Float.class;
                break;
            case "D":
                bytecode.addDload(index);
                w = Double.class;
                break;
            default:
                return false;
        }
        bytecode.addInvokestatic(getType(w), "valueOf", getMethodDesc(w, BeanUtils.toPrimitiveClass(w)));
        return true;
    }

    public static String getDesc(Class<?> returnType) {
        if (returnType.isPrimitive()) {
            return getPrimitiveLetter(returnType);
        } else if (returnType.isArray()) {
            return "[" + getDesc(returnType.getComponentType());
        } else {
            return "L" + getType(returnType) + ";";
        }
    }

    public static String getType(Class<?> parameterType) {
        if (parameterType.isArray()) {
            return "[" + getDesc(parameterType.getComponentType());
        } else {
            if (!parameterType.isPrimitive()) {
                String clsName = parameterType.getName();
                return clsName.replace('.', '/');
            } else {
                return getPrimitiveLetter(parameterType);
            }
        }
    }


    public static String getPrimitiveLetter(Class<?> type) {
        if (Integer.TYPE.equals(type)) {
            return "I";
        } else if (Void.TYPE.equals(type)) {
            return "V";
        } else if (Boolean.TYPE.equals(type)) {
            return "Z";
        } else if (Character.TYPE.equals(type)) {
            return "C";
        } else if (Byte.TYPE.equals(type)) {
            return "B";
        } else if (Short.TYPE.equals(type)) {
            return "S";
        } else if (Float.TYPE.equals(type)) {
            return "F";
        } else if (Long.TYPE.equals(type)) {
            return "J";
        } else if (Double.TYPE.equals(type)) {
            return "D";
        }

        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
    }

    /**
     * 生成方法签名
     * @param returnType
     * @param params
     * @return
     */
    public static String getMethodDesc(Class<?> returnType, Class<?>... params) {
        StringBuilder sb = new StringBuilder("(");
        for (Class<?> c : params) {
            sb.append(getDesc(c));
        }
        sb.append(')');
        sb.append(getDesc(returnType));
        return sb.toString();
    }

    public static void removeAllClass(ClassPool pool) {
        pool.classes.clear();
    }
}
